package service

import (
	"log"
	"net/http"
	"time"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/gin-gonic/gin"
	vegeta "github.com/tsenart/vegeta/v12/lib"
)

var StressStatus = api.StressStatus{
	Tps: api.Tps{
		TravelServiceTps:     0,
		TravelPlanServiceTps: 0,
	},
	IsStressed: false,
}
var Attacker = vegeta.NewAttacker()
var TravelServiceMetrics vegeta.Metrics
var TravelPlanServiceMetrics vegeta.Metrics
var trainTicketAddr string

func RegisterStress(router *gin.RouterGroup) {
	chaosRouter := router.Group("tps")
	{
		chaosRouter.POST("/start", StartStress)
		chaosRouter.GET("/stop", EndStress)
		chaosRouter.GET("/status", GetStressStatus)
	}
}

func SetupTrainTicketAddr(addr string) {
	trainTicketAddr = addr
}

func StartStress(c *gin.Context) {

	if StressStatus.IsStressed {
		log.Printf("Please end this stress test first")
		api.Response(c, http.StatusInternalServerError, 500, "please end this stress test first", nil)
		return
	}

	tps := api.Tps{}
	if err := c.BindJSON(&tps); err != nil {
		log.Println("request body is illegal: ", err)
		api.Response(c, http.StatusInternalServerError, 500, "request body is illegal", nil)
		return
	}

	if tps.TravelServiceTps <= 0 && tps.TravelPlanServiceTps <= 0 {
		log.Printf("At least one Tps should be a positive integer")
		api.Response(c, http.StatusInternalServerError, 500,
			"failed to run stress tests, at least one Tps should be a positive integer", nil)
		return
	}

	header := http.Header{}
	header.Add("Accept", "application/json, text/javascript, */*; q=0.01")
	header.Add("Content-Type", "application/json")
	date := time.Now().Format("2006-01-02")
	body := []byte(`{"startPlace":"Shang Hai","endPlace":"Su Zhou","departureTime":"` + date + ` 00:00:00"}`)
	travelServiceTargeter := vegeta.NewStaticTargeter(vegeta.Target{
		Method: "POST",
		Header: header,
		Body:   body,
		URL:    trainTicketAddr + "/api/v1/travelservice/trips/left",
	})
	travelPlanServiceTargeter := vegeta.NewStaticTargeter(vegeta.Target{
		Method: "POST",
		Header: header,
		Body:   body,
		URL:    trainTicketAddr + "/api/v1/travelplanservice/travelPlan/cheapest",
	})
	StressStatus.TravelServiceTps = tps.TravelServiceTps
	StressStatus.TravelPlanServiceTps = tps.TravelPlanServiceTps

	duration := 0 * time.Second // duration=0时持续压测

	StressStatus.IsStressed = true

	if tps.TravelServiceTps > 0 {
		if tps.TravelServiceTps > 200 {
			tps.TravelServiceTps = 200
		}
		travelServiceRate := vegeta.Rate{Freq: StressStatus.TravelServiceTps, Per: time.Second}
		go func() {
			for res := range Attacker.Attack(travelServiceTargeter, travelServiceRate, duration, "stress on travel-service") {
				TravelServiceMetrics.Add(res)
			}
		}()
	}

	if tps.TravelPlanServiceTps > 0 {
		if tps.TravelPlanServiceTps > 200 {
			tps.TravelPlanServiceTps = 200
		}
		travelPlanServiceRate := vegeta.Rate{Freq: StressStatus.TravelPlanServiceTps, Per: time.Second}
		go func() {
			for res := range Attacker.Attack(travelPlanServiceTargeter, travelPlanServiceRate, duration, "stress on travel-plan-service") {
				TravelPlanServiceMetrics.Add(res)
			}
		}()
	}

	api.ResponseOK(c, "", nil)
}

func EndStress(c *gin.Context) {

	if !StressStatus.IsStressed {
		log.Printf("Stress testing has not started yet")
		api.Response(c, http.StatusInternalServerError, 500, "stress testing has not yet started", nil)
		return
	}

	Attacker.Stop()
	TravelServiceMetrics.Close()
	TravelPlanServiceMetrics.Close()
	StressStatus.IsStressed = false
	StressStatus.TravelPlanServiceTps = 0
	StressStatus.TravelServiceTps = 0

	Attacker = vegeta.NewAttacker()

	api.ResponseOK(c, "", api.Data{
		"travel-service-report": api.StressReport{
			MeanLatency: float64(TravelServiceMetrics.Latencies.Mean) / 1000000000,
			RequestNum:  int(TravelServiceMetrics.Requests),
			RequestRate: TravelServiceMetrics.Rate,
			Throughput:  TravelServiceMetrics.Throughput,
			SuccessRate: TravelServiceMetrics.Success,
		},
		"travel-plan-service-report": api.StressReport{
			MeanLatency: float64(TravelPlanServiceMetrics.Latencies.Mean) / 1000000000,
			RequestNum:  int(TravelPlanServiceMetrics.Requests),
			RequestRate: TravelPlanServiceMetrics.Rate,
			Throughput:  TravelPlanServiceMetrics.Throughput,
			SuccessRate: TravelPlanServiceMetrics.Success,
		},
	})

	TravelServiceMetrics = vegeta.Metrics{}
	TravelPlanServiceMetrics = vegeta.Metrics{}
}

func GetStressStatus(c *gin.Context) {
	api.ResponseOK(c, "", api.Data{
		"stressStatus": StressStatus,
	})
}
